package com.hellobike.haxing.common.util;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Description redis缓存工具类
 * @Author ZLB
 * @Date 2020/1/4
 * @Version 1.0
 **/
@Slf4j
@Component
public class RedisCacheUtil {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void set(String key, String value, long expire, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, expire, timeUnit);
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, String value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 单个查询
     *
     * @param keyPattern
     * @param loadId
     * @param typeReference
     * @param expireTime
     * @param cacheLoader
     * @param <K>
     * @param <T>
     * @return
     */
    public <K, T> T get(String keyPattern, K loadId, TypeReference<T> typeReference, Long expireTime, RedisCacheLoader<K, T> cacheLoader) {
        if (loadId == null) {
            return null;
        }
        String key = keyPattern.replace("{}", loadId.toString());
        String json = redisTemplate.opsForValue().get(key);
        T obj;
        if (!Strings.isNullOrEmpty(json)) {
            obj = JSON.parseObject(json, typeReference);
        } else {
            obj = cacheLoader.load(loadId);
            json = JSON.toJSONString(obj);
            //防缓存击穿、缓存穿透
            expireTime = obj == null ? RandomUtil.randomLong(100L, 200L) : expireTime + RandomUtil.randomLong(100L);
            redisTemplate.opsForValue().set(key, json, expireTime, TimeUnit.SECONDS);
        }
        return obj;
    }

    /**
     * 批量查询(以List形式返回)
     *
     * @param keyPattern
     * @param loadIdList
     * @param typeReference
     * @param expireTime
     * @param cacheLoader
     * @param <K>
     * @param <T>
     * @return
     */
    public <K, T> List<T> batchGet(String keyPattern, List<K> loadIdList, TypeReference<T> typeReference, Long expireTime, RedisCacheLoader<List<K>, Map<K, T>> cacheLoader) {
//        if (CollectionUtils.isEmpty(loadIdList)) {
//            return new ArrayList<>();
//        }
//        loadIdList = loadIdList.stream().distinct().filter(id -> id != null).collect(Collectors.toList());
//        List<String> keyList = loadIdList.stream().map(loadId -> keyPattern.replace("{}", loadId.toString())).collect(Collectors.toList());
//        List<String> jsonList = redisTemplate.opsForValue().multiGet(keyList);
//        //查询结果集
//        LinkedHashMap<K, T> map = new LinkedHashMap<>();
//        //缓存中未命中的id的集合
//        List<K> notHitIdList = new ArrayList<>();
//        for (int i = 0; i < jsonList.size(); i++) {
//            String json = jsonList.get(i);
//            K loadId = loadIdList.get(i);
//            //即使缓存未命中也先放入有序map中，以保证顺序
//            map.put(loadId, JSON.parseObject(json, typeReference));
//            //未命中的id加入集合，准备从库中批量查询
//            if (Strings.isNullOrEmpty(json)) {
//                notHitIdList.add(loadId);
//            }
//        }
//        //从库存批量查询未缓存未命中的数据
//        if (notHitIdList.size() > 0) {
//            Map<K, T> dbDataMap = cacheLoader.load(notHitIdList);
//            //补充到查询结果集
//            map.putAll(dbDataMap);
//            //载入缓存
//            //将数据库中也不存在的数据以空值存入redis，以防缓存穿透
//            if (notHitIdList.size() > dbDataMap.size()) {
//                notHitIdList.removeAll(dbDataMap.keySet());
//                for (K k : notHitIdList) {
//                    dbDataMap.put(k, null);
//                }
//            }
//            for (Map.Entry<K, T> ktEntry : dbDataMap.entrySet()) {
//                String key = keyPattern.replace("{}", ktEntry.getKey().toString());
//                String json = JSON.toJSONString(ktEntry.getValue());
//                expireTime = ktEntry.getValue() == null ? RandomUtil.randomLong(100L, 200L) : expireTime + RandomUtil.randomLong(100L);
//                redisTemplate.opsForValue().set(key, json, expireTime, TimeUnit.SECONDS);
//            }
//        }

        LinkedHashMap<K, T> map = batchGetMap(keyPattern, loadIdList, typeReference, expireTime, cacheLoader);

        return map.values().stream().filter(v -> v != null).collect(Collectors.toList());
    }

    /**
     * 批量查询（以Map形式返回）
     *
     * @param keyPattern
     * @param loadIdList
     * @param typeReference
     * @param expireTime
     * @param cacheLoader
     * @param <K>
     * @param <T>
     * @return
     */
    public <K, T> LinkedHashMap<K, T> batchGetMap(String keyPattern, List<K> loadIdList, TypeReference<T> typeReference, Long expireTime, RedisCacheLoader<List<K>, Map<K, T>> cacheLoader) {
        if (CollectionUtils.isEmpty(loadIdList)) {
            return new LinkedHashMap<>();
        }
        loadIdList = loadIdList.stream().distinct().filter(id -> id != null).collect(Collectors.toList());
        List<String> keyList = loadIdList.stream().map(loadId -> keyPattern.replace("{}", loadId.toString())).collect(Collectors.toList());
        List<String> jsonList = redisTemplate.opsForValue().multiGet(keyList);
        //查询结果集
        LinkedHashMap<K, T> map = new LinkedHashMap<>();
        //缓存中未命中的id的集合
        List<K> notHitIdList = new ArrayList<>();
        for (int i = 0; i < jsonList.size(); i++) {
            String json = jsonList.get(i);
            K loadId = loadIdList.get(i);
            //即使缓存未命中也先放入有序map中，以保证顺序
            map.put(loadId, JSON.parseObject(json, typeReference));
            //未命中的id加入集合，准备从库中批量查询
            if (Strings.isNullOrEmpty(json)) {
                notHitIdList.add(loadId);
            }
        }
        //从库存批量查询未缓存未命中的数据
        if (notHitIdList.size() > 0) {
            Map<K, T> dbDataMap = cacheLoader.load(notHitIdList);
            //补充到查询结果集
            map.putAll(dbDataMap);
            //载入缓存
            //将数据库中也不存在的数据以空值存入redis，以防缓存穿透
            if (notHitIdList.size() > dbDataMap.size()) {
                notHitIdList.removeAll(dbDataMap.keySet());
                for (K k : notHitIdList) {
                    dbDataMap.put(k, null);
                }
            }
            for (Map.Entry<K, T> ktEntry : dbDataMap.entrySet()) {
                String key = keyPattern.replace("{}", ktEntry.getKey().toString());
                String json = JSON.toJSONString(ktEntry.getValue());
                expireTime = ktEntry.getValue() == null ? RandomUtil.randomLong(100L, 200L) : expireTime + RandomUtil.randomLong(100L);
                redisTemplate.opsForValue().set(key, json, expireTime, TimeUnit.SECONDS);
            }
        }

        return map;
    }

    /**
     * 删除缓存数据
     *
     * @param keyPattern
     * @param id
     * @param <K>
     */
    public <K> void delete(String keyPattern, K id) {
        if (id == null) {
            return;
        }
        String key = keyPattern.replace("{}", id.toString());
        redisTemplate.delete(key);
    }

    /**
     * 批量删除缓存数据
     *
     * @param keyPattern
     * @param idList
     * @param <K>
     */
    public <K> void batchDelete(String keyPattern, List<K> idList) {
        if (CollectionUtils.isEmpty(idList)) {
            return;
        }
        idList = idList.stream().distinct().filter(id -> id != null).collect(Collectors.toList());
        List<String> keyList = idList.stream().map(id -> keyPattern.replace("{}", id.toString())).collect(Collectors.toList());
        redisTemplate.delete(keyList);
    }

    /**
     * 加锁
     *
     * @param key
     * @param value
     * @param expireSeconds
     * @return
     */
    public boolean lock(String key, String value, long expireSeconds) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);
    }

    /**
     * 尝试加锁直至超时
     *
     * @param key
     * @param value
     * @param expireSeconds
     * @param timeoutSeconds
     * @return
     */
    public boolean tryLock(String key, String value, long expireSeconds, long timeoutSeconds) {
        Long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
        do {
            if (lock(key, value, expireSeconds)) {
                return true;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } while (System.currentTimeMillis() < timeout);
        return false;
    }

    /**
     * 释放锁
     *
     * @param key
     * @param value
     * @return
     */
    public boolean unLock(String key, String value) {
        if (value != null && !Objects.equals(value, redisTemplate.opsForValue().get(key))) {
            //非自身所持锁，无权释放
            return false;
        }
        return redisTemplate.delete(key);
    }
}
